#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>

#define NUM 30
#define SIZE 20
#define SEP " "

char s[NUM];            //s可以是全局变量，当作"内存"使用
char g_env[SIZE];

#define NONE_REDIR 0
#define OUTPUT_REDIR 1
#define APPEND_REDIR 2
#define INPUT_REDIR 3

int reDir = NONE_REDIR;

char* CheckRedir(char* start)
{
    assert(start);
    char* end = start + strlen(start) - 1;
    //printf("%s\n", start);
    while(end >= start)
    {
        if(*end == '>')
        {
            if(*(end - 1) == '>')
            {
                *(end - 1) = '\0';
                reDir = APPEND_REDIR;
            }
            else
            {
                *end = '\0';
                reDir = OUTPUT_REDIR;
            }
            ++end;
            break;
        }
        else if(*end == '<')
        {
            *end = '\0';
            ++end;
            reDir = INPUT_REDIR;
            break;
        }
        else 
        {
            --end;
        }
    }
    //printf("%s\n", start);
    if(end >= start)
    {
        //处理空格情况
        while(*end == ' ')
            ++end;
        return end;
    }
    reDir = NONE_REDIR;
    return NULL;
}

int main()
{
    while(1)
    {
        printf("[root@localhost shell]#");          //此语句最后面不能加\n,否则会换行
        fflush(stdout);
        //char s[NUM];            //s不是存储char*的,在下面的while循环中只是用来存储字符串
        memset(s, '\0', sizeof s);//需要用memset对s进行初始化，否则s里面可能是随机值或者上次遗留下来的数据
        
        ////测试
        //int i = 0;
        //for(i = 0; i < NUM; ++i)
        //    printf("%c ", s[i]);
        
        fgets(s, sizeof s, stdin);       //此处不许加while循环，因为只进行一次
        if(strcmp(s, "\n") == 0)        //如果输入的只有换行符时，重新进入循环。比较的是\n而不是\0
            continue;
        s[strlen(s) - 1] = '\0';
        //printf("%s\n", s);
        
        //重定向
        char* sep = CheckRedir(s);      //此语句应在下方的strtok语句前执行，因为该语句会将内容切割
        
        char* _ele[SIZE] = { NULL };        //_ele不能加const修饰*_ele,否则与下方的execvp参数不符
        //printf("%s\n", s);
        _ele[0] = strtok(s, SEP);        //SEP需要的是字符串而不是字符
        //printf("%s\n", s);
        //printf("%s\n", _ele[0]);
        int index = 1;

        if(strcmp(_ele[0], "ll") == 0)
        {
            _ele[0] = (char*)"ls";
            _ele[index++] = (char*)"-l";
        }
        if(strcmp(_ele[0], "ls") == 0)
        {
            _ele[index++] = (char*)"--color=auto";
        }

        while(_ele[index++] = strtok(NULL, SEP));        
        ////测试
        //int i = 0;
        //for(i = 0; i < index; ++i)
        //    printf("_ele[%d] = %s\n", i, _ele[i]);
        
        //内建命令
        if(strcmp(_ele[0], "cd") == 0)
        {
            if(_ele[1] != NULL)         //最好加上这层判断
                chdir(_ele[1]);         //需要使用chdir而不是execvp,否则父进程执行完该指令后就直接退出了
            continue;
        }

        if(strcmp(_ele[0], "export") == 0 && _ele[1] != NULL)       //需要加判断_ele[1]是否为空的条件
        {
            strcpy(g_env, _ele[1]);     //g_env不需要加[]和下标，直接拷贝即可
            int ret = putenv(g_env);
            if(ret == 0)
            {
                printf("export %s setting, success\n", g_env);
            }
            continue;
        }

        pid_t id = fork();
        if(id == 0)
        {
            //printf("%s\n", sep);
            if(sep != NULL)
            {
                //printf("%s\n", sep);
                int fd = -1;
                if(reDir == OUTPUT_REDIR)
                {
                    fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);
                    dup2(fd, 1);
                }
                else if(reDir == APPEND_REDIR)
                {
                    fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);
                    dup2(fd, 1);
                }
                else if(reDir == INPUT_REDIR)
                {
                    fd = open(sep, O_RDONLY);
                    dup2(fd, 0);
                }
                else
                {
                    printf("bug?\n");
                }
            }
            //printf("子进程执行代码\n");
            execvp(_ele[0], _ele);        //此处应该用v而不是用l，应该_ele是一个指针数组
            exit(1);            //加上这语句，当子进程切换进程失败时直接退出
        }
        int status = 0;
        pid_t res = waitpid(id, &status, 0);
        if(res > 0)     //加上这语句。当等待子进程成功时才执行输出语句
            printf("子进程退出码:%d\n", WEXITSTATUS(status));
    }

    return 0;
}
